Understanding Lazy Object Instantiation

Note This section assumes you are comfortable with the topics of .NET generics and .NET delegates. If that’s not the case, you may want to return to this section once you have completed Chapters 10 and 11.

When you are creating classes, you may occasionally need to account for a particular member variable in code, which may never actually be needed, in that the object user may not call the method (or property) that makes use of it. Fair enough. However, this can be very problematic if the member variable in question requires a large amount of memory to be instantiated.

For example, assume you are writing a class that encapsulates the operations of a digital music player. In addition to the expected methods, such as Play(), Pause(), and Stop(), you also want to provide the ability to return a collection of Song objects (via a class named AllTracks), which represents every single digital music file on the device.

If you’d like to follow along, create a new Console Application named LazyObjectInstantiation, and define the following class types:

// Represents a single song.
class Song
{
    public string Artist { get; set; }
    public string TrackName { get; set; }
    public double TrackLength { get; set; }
}

// Represents all songs on a player.
class AllTracks
{
    // Our media player can have a maximum
    // of 10,000 songs.
    private Song[] allSongs = new Song[10000];

    public AllTracks()
    {
        // Assume we fill up the array
        // of Song objects here.
        Console.WriteLine("Filling up the songs!");
    }
}

// The MediaPlayer has-a AllTracks object.
class MediaPlayer
{
    // Assume these methods do something useful.
    public void Play() { /* Play a song */ }
    public void Pause() { /* Pause the song */ }
    public void Stop() { /* Stop playback */ }
    
    private AllTracks allSongs = new AllTracks();
    
    public AllTracks GetAllTracks()
    {
        // Return all of the songs.
        return allSongs;
    }
}

The current implementation of MediaPlayer makes the assumption that the object user will want to obtain a list of songs via the GetAllTracks() method. Well, what if the object user does not need to obtain this list? No matter, the AllTracks member variable will create 10,000 Song objects in memory:

static void Main(string[] args)
{
    // This caller does not care about getting all songs,
    // but indirectly created 10,000 objects!
    MediaPlayer myPlayer = new MediaPlayer();
    myPlayer.Play();

    Console.ReadLine();
}

Clearly, you would rather not create 10,000 objects that nobody will use, as that will add a good deal of stress to the .NET garbage collector. While you could manually add some code to ensure the allSongs object is only created if used (perhaps using the factory method design pattern), there is an easier way.

With the release of .NET 4.0, the base class libraries provide a very interesting generic class named Lazy<>, defined in the System namespace of mscorlib.dll. This class allows you to define data that will not be created unless your code base actually makes use of it. As this is a generic class, you must specify the type of item to be created on first use, which can be any type with the .NET base class libraries or a custom type you have authored yourself. To enable lazy instantiation of the AllTracks member variable, you can simply replace this:

// The MediaPlayer has-a AllTracks object.
class MediaPlayer
{
...
    private AllTracks allSongs = new AllTracks();

    public AllTracks GetAllTracks()
    {
        // Return all of the songs.
        return allSongs;
    }
}

with this:

// The MediaPlayer has-a Lazy<AllTracks> object.
class MediaPlayer
{
...
    private Lazy<AllTracks> allSongs = new Lazy<AllTracks>();

    public AllTracks GetAllTracks()
    {
        // Return all of the songs.
        return allSongs.Value;
    }
}

Beyond the fact that we are now representing the AllTracks member variable as a Lazy<> type, notice that the implementation of the previous GetAllTracks() method has also been updated. Specifically, we must make use of the read-only Value property of the Lazy<> class to obtain the actual stored data (in this case, the AllTracks object that is maintaining the 10,000 Song objects).

With this simple update, notice how the following updated Main() method will indirectly allocate the Song objects only if GetAllTracks() is indeed called:

static void Main(string[] args)
{
    Console.WriteLine("***** Fun with Lazy Instantiation *****\n");

    // No allocation of AllTracks object here!
    MediaPlayer myPlayer = new MediaPlayer();
    myPlayer.Play();
    
    // Allocation of AllTracks happens when you call GetAllTracks().
    MediaPlayer yourPlayer = new MediaPlayer();
    AllTracks yourMusic = yourPlayer.GetAllTracks();

    Console.ReadLine();
}

Customizing the Creation of the Lazy Data

When you declare a Lazy<> variable, the actual internal data type is created using the default constructor:

// Default constructor of AllTracks is called when the Lazy<>
// variable is used.
private Lazy<AllTracks> allSongs = new Lazy<AllTracks>();

While this might be fine in some cases, what if the AllTracks class had some additional constructors, and you want to ensure the correct one is called? Furthermore, what if you have some extra work do to (beyond simply creating the AllTracks object) when the Lazy<> variable is made? As luck would have it, the Lazy<> class allows you to specify a generic delegate as an optional parameter, which will specify a method to call during the creation of the wrapped type.

The generic delegate in question is of type System.Func<>, which can point to a method that returns the same data type being created by the related Lazy<> variable and can take up to 16 arguments (which are typed using generic type parameters). In most cases, you will not need to specify any parameters to pass to the method pointed to by Func<>. Furthermore, to greatly simplify the use of the required Func<>, I’d recommend using a lambda expression (see Chapter 11 for details regarding the delegate/lambda relationship).

With this in mind, here is a final version of MediaPlayer that adds a bit of custom code when the wrapped AllTracks object is created. Remember, this method must return a new instance of the type wrapped by Lazy<> before exiting, and you can use any constructor you choose (here, we are still invoking the default constructor of AllTracks):

class MediaPlayer
{
...
    // Use a lambda expression to add additional code
    // when the AllTracks object is made.
    private Lazy<AllTracks> allSongs = new Lazy<AllTracks>( () =>
        {
            Console.WriteLine("Creating AllTracks object!");
            return new AllTracks();
        }
    );

    public AllTracks GetAllTracks()
    {
        // Return all of the songs.
        return allSongs.Value;
    }
}

Sweet! Hopefully you can see the usefulness of the Lazy<> class. Essentially, this new generic class allows you to ensure expensive objects are only allocated when the object user requires them. If you find this topic useful for your projects, you may also want to look up the System.Lazy<> class in the .NET Framework 4.0 SDK documentation for further examples of how to program for "lazy instantiation".

Source Code The LazyObjectInstantiation project is included under the Chapter 8 subdirectory.